"""
先用函数的方式实现网络的正向计算和反向误差传播，权值更新，并实现错误样本可视化。
"""
import numpy as np
from sklearn import datasets
import matplotlib.pyplot as plt
from sklearn.metrics import accuracy_score

# 数据加载
np.random.seed(0)
X, y = datasets.make_moons(200, noise=0.20)
# 源数据绘图
plt.figure(1)
plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.Spectral)
plt.title('Origin')
plt.xlim(-1.5, 2.5)
plt.ylim(-1.0, 1.5)


def sigmod(x):
    return 1.0 / (1 + np.exp(-1 * x))


def forward(x, w, b):
    return sigmod(np.dot(x, w) + b)


def nn(data, label, ratio=0.01, n_max=2000):
    input_dim = np.shape(data)[1]  # 输入神经元
    hide_dim = 8  # 隐藏层神经元
    out_dim = 2  # 输出层神经元
    # 权重初始化
    W1 = np.random.randn(input_dim, hide_dim) / np.sqrt(input_dim)
    b1 = np.zeros((1, hide_dim))
    W2 = np.random.randn(hide_dim, out_dim) / np.sqrt(hide_dim)
    b2 = np.zeros((1, out_dim))
    n = 0
    while n < n_max:
        a = forward(data, W1, b1)
        y = forward(a, W2, b2)
        error_out = y * (1 - y) * (label - y)
        error_hide = a * (1 - a) * np.dot(error_out, W2.T)
        W1 = W1 + ratio * np.dot(data.T, error_hide)
        b1 = b1 + ratio * np.sum(error_hide, axis=0)
        W2 = W2 + ratio * np.dot(a.T, error_out)
        b2 = b2 + ratio * np.sum(error_out, axis=0)
        n = n + 1
    y_pred = np.argmax(y, axis=1).reshape(np.shape(data)[0], 1)
    return y_pred


def error_show(data, ture_label, pred_label):
    error_index = [i for i in range(int(np.shape(pred_label)[0])) if ture_label[i] != pred_label[i]]
    return data[error_index, :], pred_label[error_index, :]


z = np.zeros((np.shape(X)[0], 2))
z[np.where(y == 0), 0] = 1
z[np.where(y == 1), 1] = 1
out_pred = nn(X, z)
acc = accuracy_score(y, out_pred)
print("准确率:%f" % acc)
plt.figure(2)
plt.scatter(X[:, 0], X[:, 1], c=out_pred, cmap=plt.cm.Spectral)
plt.title("Predicted")
plt.xlim(-1.5, 2.5)
plt.ylim(-1.0, 1.5)
# 错误样本可视化
error_data, error_label = error_show(X, y, out_pred)
plt.figure(3)
plt.scatter(error_data[:, 0], error_data[:, 1], c=error_label, cmap=plt.cm.Spectral)
plt.title("Predicted incorrect data")
plt.xlim(-1.5, 2.5)
plt.ylim(-1.0, 1.5)
plt.show()

"""
实现基于类的神经网络程序,并实现错误样本可视化
"""


class nn:
    def __init__(self, data, label):
        self.data = data
        self.label = label

    def sigmod(self, x):
        return 1.0 / (1 + np.exp(-1 * x))

    def forward(self, x, w, b):
        return self.sigmod(np.dot(x, w) + b)

    def divide(self, hide_dim, out_dim, n_max=2000, ratio=0.01):
        np.random.seed(0)
        input_dim = np.shape(self.data)[1]
        # 权重初始化
        W1 = np.random.randn(input_dim, hide_dim) / np.sqrt(input_dim)
        b1 = np.zeros((1, hide_dim))
        W2 = np.random.randn(hide_dim, out_dim) / np.sqrt(hide_dim)
        b2 = np.zeros((1, out_dim))
        n = 0
        while n < n_max:
            a = self.forward(self.data, W1, b1)  # 隐藏层
            y = self.forward(a, W2, b2)  # 输出层
            error_out = y * (1 - y) * (self.label - y)
            error_hide = a * (1 - a) * np.dot(error_out, W2.T)
            W1 = W1 + ratio * np.dot(self.data.T, error_hide)
            b1 = b1 + ratio * np.sum(error_hide, axis=0)
            W2 = W2 + ratio * np.dot(a.T, error_out)
            b2 = b2 + ratio * np.sum(error_out, axis=0)
            n = n + 1
        y_pred = np.argmax(y, axis=1).reshape(np.shape(self.data)[0], 1)
        return y_pred

    def error(self, ture_label, pred_label):
        error_index = [i for i in range(int(np.shape(pred_label)[0])) if ture_label[i] != pred_label[i]]
        return self.data[error_index, :], pred_label[error_index, :]


out_pred = nn(X, z).divide(hide_dim=8, out_dim=2)
acc = accuracy_score(y, out_pred)
print("准确率:%f" % acc)
plt.figure(2)
plt.scatter(X[:, 0], X[:, 1], c=out_pred, cmap=plt.cm.Spectral)
plt.title("Predicted")
plt.xlim(-1.5, 2.5)
plt.ylim(-1.0, 1.5)
# 错误样本可视化
error_data, error_label = nn(X, z).error(y, out_pred)
plt.figure(3)
plt.scatter(error_data[:, 0], error_data[:, 1], c=error_label, cmap=plt.cm.Spectral)
plt.title("Predicted incorrect data")
plt.xlim(-1.5, 2.5)
plt.ylim(-1.0, 1.5)
plt.show()
